#!/usr/bin/python
# encoding: utf-8
import os, sys
from contextlib import asynccontextmanager
from datetime import date
import inspect
import json
from typing import List
from fastapi.openapi.docs import get_swagger_ui_html
import fastapi_jsonrpc as jsonrpc
from fastapi import Header, applications, Request
from src.logger import myLogger
import datetime
import numpy as np
import pandas as pd
import math
import ast

from iotdb.Session import Session
from iotdb.template.MeasurementNode import MeasurementNode
from iotdb.template.Template import Template
from iotdb.utils.BitMap import BitMap
from iotdb.utils.IoTDBConstants import TSDataType, TSEncoding, Compressor
from iotdb.utils.Tablet import Tablet
from iotdb.utils.NumpyTablet import NumpyTablet
#from peewee import fn, JOIN, chunked
from configparser import ConfigParser
import requests

import sqlite3

import atexit

logger = myLogger('api', 'trace.log')

def swagger_url_patch(*args, **kwargs):
    return get_swagger_ui_html(
        *args, **kwargs,
        swagger_js_url='https://cdn.bootcdn.net/ajax/libs/swagger-ui/5.6.2/swagger-ui-bundle.js',
        swagger_css_url='https://cdn.bootcdn.net/ajax/libs/swagger-ui/5.6.2/swagger-ui.css'
    )

applications.get_swagger_ui_html = swagger_url_patch

# errors
class AuthError(jsonrpc.BaseError):
    CODE = 7000
    MESSAGE = 'Auth error'


class AccountNotFound(jsonrpc.BaseError):
    CODE = 6000
    MESSAGE = 'Account not found'

class NotPermitted(jsonrpc.BaseError):
    CODE = 6001
    MESSAGE = 'Not Permitted'

class MyError(jsonrpc.BaseError):
    CODE = -1
    MESSAGE = ''

    def __init__(self, data=None, code: int = None, msg: str = None):
        if code:
            self.CODE = code
        if msg:
            self.MESSAGE = msg
        super().__init__(data)

# JSON-RPC middlewares
@asynccontextmanager
async def logging_middleware(ctx: jsonrpc.JsonRpcContext):
    print(ctx.http_request.headers)
    logger.info('Request: %r', ctx.raw_request)
    try:
        yield
    finally:
        logger.info('Response: %r', ctx.raw_response)

# JSON-RPC entrypoint
common_errors = [AccountNotFound, AuthError, NotPermitted]
common_errors.extend(jsonrpc.Entrypoint.default_errors)

api = jsonrpc.Entrypoint(
    # Swagger shows for entrypoint common parameters gathered by dependencies and common_dependencies:
    #    - json-rpc-parameter 'account_id'
    #    - header parameter 'user-auth-token'
    '/',
    name='IoTDB Data Base',
    errors=common_errors,
    middlewares=[logging_middleware],
    # this dependencies called once for whole json-rpc batch request
    # dependencies=[Depends(get_auth_user)],
    # this dependencies called separately for every json-rpc request in batch request
    # common_dependencies=[Depends(get_account)],
)

#------------- special api code starts here--------------------------------

#------------define some device ID--------------------
idAC1 = 'Acm-M1-7cdfa1b7894c'     # room204 AC
idAC2 = 'Acm-M1-7cdfa1d3c968'     # teacher rest room AC
idTotal = 'Mt3-M1-7cdfa1b87784'     # total
idPV = 'Mt3-M1-7cdfa1b860d4'     #PV
idChar = 'Mt3-M1-84f703123af8'     # charging
idHP = 'Mt3-M1-84f703123a84'     # heat pump
idWind = 'Mt1-M1-7cdfa1b8a66c'  # wind turbine

#------------IoTDB database connection ----------------
def iotdbconnect():

    ip = "58.246.105.218"
    port_ = "6666"
    username_ = "retrieve"
    password_ = "!2#4QwEr"
    session = Session(ip, port_, username_, password_)
    session.open(False)
    return session

# timePoint format: '2024-01-19 13:00:00'
def basicGetOneTimePointQuery_1(devid:str, timePoint:str, dataType:str='P'):
    try:
        session = iotdbconnect()
    except:
        raise MyError(msg='Could not connect to the IoTDB database')
    else:

        qureystr = 'select last `%s`.%s from root.** where time <= %s' % (devid, dataType, timePoint)
        res2 = session.execute_query_statement(qureystr)
        df2 = res2.todf()

        qureystr = 'select `%s`.%s from root.** where time >= %s limit 1' % (devid, dataType, timePoint)
        res3 = session.execute_query_statement(qureystr)
        df3 = res3.todf()

        if df2.shape[0] == 0 and df3.shape[0] == 0:
            raise MyError(msg='Could not find the %s value for device %s at time %s' % (dataType, devid, timePoint))

        if df2.shape[0] != 0:
            return float(df2.iloc[0, 2])

        if df3.shape[0] != 0:
            return float(df3.iloc[0, 1])

    finally:
        session.close()

def basicGetOneTimePeriodQuery(devid: str, starttime: str, endtime: str, accuracy: str = 'k5', dataType: str = 'P',
                               storageGroupPath: str = 'root'):
    try:
        session = iotdbconnect()
    except:
        raise MyError(msg='Could not connect to the IoTDB database')
    else:

        if accuracy[0] == 'k':
            queryTimeInterval = '%sm' % (accuracy[1:])
        elif accuracy == 'day':
            queryTimeInterval = '1d'
        elif accuracy == 'month':
            queryTimeInterval = '1mo'
        else:
            raise MyError(msg='The accuracy %s is not defined correctly' % (accuracy))

        qureystr = 'select avg(`%s`.%s) from %s.** group by ([%s, %s), %s) fill(0.0)' % (
        devid, dataType, storageGroupPath,
        starttime, endtime,
        queryTimeInterval)
        res2 = session.execute_query_statement(qureystr)
        df2 = res2.todf()

        if df2.empty:  # if it is empty, directly return the df2
            '''
            qureystr = 'select avg(`%s`.%s) from %s.** group by ([%s, %s), %s) fill(0.0)' % (devid, dataType, storageGroupPath,
                                                                                     starttime, endtime,
                                                                                     queryTimeInterval)
            res2 = session.execute_query_statement(qureystr)
            df2 = res2.todf()
            '''
            return df2

        '''
        for idx in range(df2.shape[0]):

            if np.isnan(df2.iloc[idx, 1]):
                df2.iloc[idx, 1] = 0.0
        '''
        nonanidxlst = []
        nonanvaluelst = []
        fullidxlst = []
        for idx in range(df2.shape[0]):
            '''
            df2.iloc[idx, 0] = datetime.datetime.strftime(datetime.datetime.fromtimestamp(df2.iloc[idx, 0] / 1000),
                                                        '%Y-%m-%d %H:%M:%S')
            '''
            fullidxlst.append(idx)
            if not np.isnan(df2.iloc[idx, 1]):
                nonanidxlst.append(idx)
                nonanvaluelst.append(df2.iloc[idx, 1])

        # ---------do interpolation----------------
        fullvalues = np.interp(fullidxlst, nonanidxlst, nonanvaluelst)
        for idx in range(df2.shape[0]):
            df2.iloc[idx, 1] = fullvalues[idx]

        return df2

    finally:
        session.close()


def basicGetOneTimePeriodQueryNdArray(devid: str, starttime: str, endtime: str, accuracy: str = 'k5',
                                      dataType: str = 'P', storageGroupPath: str = 'root'):
    try:
        session = iotdbconnect()
    except:
        raise MyError(msg='Could not connect to the IoTDB database')
    else:

        if accuracy[0] == 'k':
            queryTimeInterval = '%sm' % (accuracy[1:])
        elif accuracy == 'day':
            queryTimeInterval = '1d'
        elif accuracy == 'month':
            queryTimeInterval = '1mo'
        else:
            raise MyError(msg='The accuracy %s is not defined correctly' % (accuracy))

        qureystr = 'select avg(`%s`.%s) from %s.** group by ([%s, %s), %s) fill(0.0)' % (
        devid, dataType, storageGroupPath,
        starttime, endtime,
        queryTimeInterval)
        res2 = session.execute_query_statement(qureystr)
        df2 = res2.todf()

        if df2.empty:  # if it is empty, directly return the df2
            '''
            qureystr = 'select avg(`%s`.%s) from %s.** group by ([%s, %s), %s) fill(0.0)' % (devid, dataType, storageGroupPath,
                                                                                     starttime, endtime,
                                                                                     queryTimeInterval)
            res2 = session.execute_query_statement(qureystr)
            df2 = res2.todf()
            '''
            return df2.values[:, 1]

        '''
        for idx in range(df2.shape[0]):

            if np.isnan(df2.iloc[idx, 1]):
                df2.iloc[idx, 1] = 0.0
        '''
        nonanidxlst = []
        nonanvaluelst = []
        fullidxlst = []
        for idx in range(df2.shape[0]):
            '''
            df2.iloc[idx, 0] = datetime.datetime.strftime(datetime.datetime.fromtimestamp(df2.iloc[idx, 0] / 1000),
                                                        '%Y-%m-%d %H:%M:%S')
            '''
            fullidxlst.append(idx)
            if not np.isnan(df2.iloc[idx, 1]):
                nonanidxlst.append(idx)
                nonanvaluelst.append(df2.iloc[idx, 1])

        # ---------do interpolation----------------
        fullvalues = np.interp(fullidxlst, nonanidxlst, nonanvaluelst)
        for idx in range(df2.shape[0]):
            df2.iloc[idx, 1] = fullvalues[idx]

        return df2.values[:, 1]

    finally:
        session.close()


# start and end Time format: '2024-01-19 00:00', '2024-01-20 00:00', should be one day from 00:00 to 00:00

def basicGetPredictValue(devid: str, startTime: str, endTime: str, accuracy: str = 'k5', dataType: str = 'P',
                         storageGroupPath: str = 'root'):
    try:
        session = iotdbconnect()
    except:
        raise MyError(msg='Could not connect to the IoTDB database')
    else:

        # predict load and compute the adjustment limit
        # get the past one week data, get average, and scale the average by * oneweekpeak/averagepeak

        # 将字符串转换为 datetime 对象
        date_time_obj = datetime.datetime.strptime(startTime, "%Y-%m-%d %H:%M:%S")

        # 计算一周前的日期和时间
        one_week_ago = date_time_obj - datetime.timedelta(weeks=1)

        # 将结果转换回字符串格式
        one_week_ago_str = one_week_ago.strftime("%Y-%m-%d %H:%M:%S")

        # print("一周前的日期和时间:", one_week_ago_str)

        # get the past one week data
        dftmp = basicGetOneTimePeriodQuery(devid, one_week_ago_str, startTime, accuracy, dataType)
        ndtmp = abs(dftmp.values[:, 1])  # (2016,)
        # reshape it to every day, for 7 days
        ndarray_new = ndtmp.reshape(7, 288)
        # average the 7 days data
        ndaverage = np.mean(ndarray_new, axis=0)  # shape is (288,)

        if abs(np.max(ndaverage)) > 0.1:  # for the data all zeros, no need to rescale
            # rescale the average data and make the average max value the same as the one week data max value
            ndaverage = ndaverage * np.max(ndarray_new) / np.max(ndaverage)

        return ndaverage

    finally:
        session.close()


# the ndDataCurve is a one-dimesion nd array with dimesion (ndim, )
def basicGetUpDownCap(ndDataCurve):
    ndim = ndDataCurve.shape[0]
    # get the adjust limit
    data_capup_real = ndDataCurve * (1.12 + np.random.randn(ndim, ) * 0.02)  # each cap up

    data_capup_real = np.where(data_capup_real < ndDataCurve, ndDataCurve + (ndDataCurve - data_capup_real),
                               data_capup_real)

    data_mean = np.mean(ndDataCurve) * np.ones(ndDataCurve.shape[0])

    data_capup_real = np.where(data_capup_real < data_mean, data_capup_real + (data_mean - data_capup_real),
                               data_capup_real)

    data_capdown_real = ndDataCurve * (0.88 + np.random.randn(ndim, ) * 0.02)  # each load cap down

    return ndDataCurve, data_capup_real, data_capdown_real

#---------------------JSON-RPC methods of this entrypoint-------------------------------

@api.method()
# currTime should be '2025-01-10 10:00:00' e
def getVirtualPlantSummary(currTime: str, dataType: str = 'P',
                              storageGroupPath: str = 'root')-> List[dict]:

    resDict = {}
    resDict['VirtualUnitNum'] = '4'

    sysCurrData = basicGetOneTimePointQuery_1(idTotal, currTime,  dataType)
    ACCurrData = basicGetOneTimePointQuery_1(idAC1, currTime,  dataType)
    ACCurrData += basicGetOneTimePointQuery_1(idAC2, currTime, dataType)
    HPCurrData = basicGetOneTimePointQuery_1(idHP, currTime, dataType)
    PVCurrData = basicGetOneTimePointQuery_1(idPV, currTime, dataType)
    WindCurrData = abs(basicGetOneTimePointQuery_1(idWind, currTime, dataType))

    #get up and down cap
    totalCapUpDelta = ACCurrData*0.12 + HPCurrData*0.12
    totalCapDownDelta = ACCurrData * 0.12 + HPCurrData * 0.12 + PVCurrData + WindCurrData

    totalCapUpReal = sysCurrData + totalCapUpDelta
    totalCapDownReal = sysCurrData - totalCapDownDelta

    resDict['CapUpReal'] = '%.2f' % (totalCapUpReal)

    resDict['CapDownReal'] = '%.2f' % (totalCapDownReal)

    resList = []
    resList.append(resDict)

    return resList


@api.method()
# currTime should be '2025-01-10 10:00:00'
def getSystemCurrData(currTime: str, dataType: str = 'P',
                           storageGroupPath: str = 'root') -> List[dict]:

    sysCurrData = basicGetOneTimePointQuery_1(idTotal, currTime, dataType)
    ACCurrData = basicGetOneTimePointQuery_1(idAC1, currTime, dataType)
    ACCurrData += basicGetOneTimePointQuery_1(idAC2, currTime, dataType)
    HPCurrData = basicGetOneTimePointQuery_1(idHP, currTime, dataType)
    PVCurrData = basicGetOneTimePointQuery_1(idPV, currTime, dataType)
    WindCurrData = abs(basicGetOneTimePointQuery_1(idWind, currTime, dataType))

    # get up and down cap
    # get up and down cap
    totalCapUpDelta = ACCurrData * 0.12 + HPCurrData * 0.12
    totalCapDownDelta = ACCurrData * 0.12 + HPCurrData * 0.12 + PVCurrData + WindCurrData

    totalCapUpReal = sysCurrData + totalCapUpDelta
    totalCapDownReal = sysCurrData - totalCapDownDelta

    ACCapUpReal = ACCurrData*1.12
    ACCapDownReal = ACCurrData*0.88

    HPCapUpReal = HPCurrData * 1.12
    HPCapDownReal = HPCurrData * 0.88

    PVCapUpReal = 0.0
    PVCapDownReal = PVCurrData

    WindCapUpReal = 0.0
    WindCapDownReal = WindCurrData

    resList = []

    resDict = {}
    resDict['dataType'] = 'Sys'
    resDict['Power'] = '%.2f' % (sysCurrData)
    resDict['CapUpReal'] = '%.2f' % (totalCapUpReal)
    resDict['CapDownReal'] = '%.2f' % (totalCapDownReal)
    resList.append(resDict)

    resDict = {}
    resDict['dataType'] = 'AC'
    resDict['Power'] = '%.2f' % (ACCurrData)
    resDict['CapUpReal'] = '%.2f' % (ACCapUpReal)
    resDict['CapDownReal'] = '%.2f' % (ACCapDownReal)
    resList.append(resDict)

    resDict = {}
    resDict['dataType'] = 'HP'
    resDict['Power'] = '%.2f' % (HPCurrData)
    resDict['CapUpReal'] = '%.2f' % (HPCapUpReal)
    resDict['CapDownReal'] = '%.2f' % (HPCapDownReal)
    resList.append(resDict)

    resDict = {}
    resDict['dataType'] = 'PV'
    resDict['Power'] = '%.2f' % (PVCurrData)
    resDict['CapUpReal'] = '%.2f' % (PVCapUpReal)
    resDict['CapDownReal'] = '%.2f' % (PVCapDownReal)
    resList.append(resDict)

    resDict = {}
    resDict['dataType'] = 'Wind'
    resDict['Power'] = '%.2f' % (WindCurrData)
    resDict['CapUpReal'] = '%.2f' % (WindCapUpReal)
    resDict['CapDownReal'] = '%.2f' % (WindCapDownReal)
    resList.append(resDict)

    return resList

@api.method()
# currTime should be '2025-01-10 10:00:00'
def getVirtualUnitCapRank(currTime: str, dataType: str = 'P',
                          storageGroupPath: str = 'root') -> List[dict]:
    sysCurrData = basicGetOneTimePointQuery_1(idTotal, currTime, dataType)
    ACCurrData = basicGetOneTimePointQuery_1(idAC1, currTime, dataType)
    ACCurrData += basicGetOneTimePointQuery_1(idAC2, currTime, dataType)
    HPCurrData = basicGetOneTimePointQuery_1(idHP, currTime, dataType)
    PVCurrData = basicGetOneTimePointQuery_1(idPV, currTime, dataType)
    WindCurrData = abs(basicGetOneTimePointQuery_1(idWind, currTime, dataType))

    # get up and down cap

    ACCapUpReal = ACCurrData * 1.12
    ACCapDownReal = ACCurrData * 0.88

    HPCapUpReal = HPCurrData * 1.12
    HPCapDownReal = HPCurrData * 0.88

    PVCapUpReal = 0.0
    PVCapDownReal = PVCurrData

    WindCapUpReal = 0.0
    WindCapDownReal = WindCurrData

    resList = []

    resDict = {}
    resDict['VirtualUnitType'] = 'AC'
    resDict['CapUpReal'] = '%.2f' % (ACCapUpReal)
    resDict['CapDownReal'] = '%.2f' % (ACCapDownReal)
    resList.append(resDict)

    resDict = {}
    resDict['VirtualUnitType'] = 'HP'
    resDict['CapUpReal'] = '%.2f' % (HPCapUpReal)
    resDict['CapDownReal'] = '%.2f' % (HPCapDownReal)
    resList.append(resDict)

    resDict = {}
    resDict['VirtualUnitType'] = 'PV'
    resDict['CapUpReal'] = '%.2f' % (PVCapUpReal)
    resDict['CapDownReal'] = '%.2f' % (PVCapDownReal)
    resList.append(resDict)

    resDict = {}
    resDict['VirtualUnitType'] = 'Wind'
    resDict['CapUpReal'] = '%.2f' % (WindCapUpReal)
    resDict['CapDownReal'] = '%.2f' % (WindCapDownReal)
    resList.append(resDict)

    return resList

@api.method()
# startTime should be '2025-01-10 00:00:00' endTime should be '2025-01-11 00:00:00'
def getSystemPredictCapUpDown(startTime: str, endTime: str, accuracy: str = 'k5', dataType: str = 'P',
                              storageGroupPath: str = 'root')-> List[dict]:
    # get AC1
    ndPredData = basicGetPredictValue(idAC1, startTime, endTime, accuracy, dataType, storageGroupPath)
    ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndPredData)
    sysCapUp = data_capup_real - ndDataCurve
    sysCapDown = ndDataCurve - data_capdown_real

    # get AC2
    ndPredData = basicGetPredictValue(idAC2, startTime, endTime, accuracy, dataType, storageGroupPath)
    ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndPredData)
    sysCapUp = sysCapUp + (data_capup_real - ndDataCurve)
    sysCapDown = sysCapDown + (ndDataCurve - sysCapDown)

    # get heat pump
    ndPredData = basicGetPredictValue(idHP, startTime, endTime, accuracy, dataType, storageGroupPath)
    ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndPredData)
    sysCapUp = sysCapUp + (data_capup_real - ndDataCurve)
    sysCapDown = sysCapDown + (ndDataCurve - sysCapDown)

    # get PV
    ndPredData = basicGetPredictValue(idPV, startTime, endTime, accuracy, dataType, storageGroupPath)
    ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndPredData)
    sysCapUp = sysCapUp + 0.0
    sysCapDown = sysCapDown + ndDataCurve

    # get wind
    ndPredData = basicGetPredictValue(idWind, startTime, endTime, accuracy, dataType, storageGroupPath)
    ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndPredData)
    sysCapUp = sysCapUp + 0.0
    sysCapDown = sysCapDown + ndDataCurve

    ndPredSys = basicGetPredictValue(idTotal, startTime, endTime, accuracy, dataType, storageGroupPath)

    ndSysCapUp = ndPredSys + sysCapUp
    ndSysCapDown = ndPredSys - sysCapDown

    #return ndPredSys, ndSysCapUp, ndSysCapDown
    dftmp = basicGetOneTimePeriodQuery(idTotal, startTime, endTime, accuracy, dataType)
    outList = []
    for idx, irow in dftmp.iterrows():
        outList.append({'predCapUpValue': '%.2f' % (ndSysCapUp[idx]),
                        'time': datetime.datetime.fromtimestamp(irow[0] / 1000).strftime("%Y-%m-%d_%H:%M")})
    for idx, irow in dftmp.iterrows():
        outList.append({'predCapDownValue': '%.2f' % (ndSysCapUp[idx]),
                        'time': datetime.datetime.fromtimestamp(irow[0] / 1000).strftime("%Y-%m-%d_%H:%M")})

    return outList

@api.method()
# startTime should be '2025-01-10 00:00:00' endTime should be '2025-01-11 00:00:00'
def getSystemOperData(startTime: str, endTime: str, accuracy: str = 'k5', dataType: str = 'P',
                      storageGroupPath: str = 'root')-> List[dict]:
    # first get system Predict data for the whole day
    ndPredSys = basicGetPredictValue(idTotal, startTime, endTime, accuracy, dataType, storageGroupPath)

    # get system Actual value
    # already get the predict value for the whole day,combine with the actual value
    current_time = datetime.datetime.now()

    # 格式化为字符串 "YYYY-MM-DD HH:MM:SS"
    formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
    formatted_day = current_time.strftime("%Y-%m-%d")
    dayStartTime = '%s 00:00:00' % (formatted_day)

    # get the data for the current day
    dftmpCurDay = basicGetOneTimePeriodQuery(idTotal, dayStartTime, formatted_time, accuracy, dataType)
    ndCurDay = dftmpCurDay.values[:, 1]  # (2016,)

    sysCombDataCurve = ndPredSys.copy()
    dimtmp = ndCurDay.shape[0]
    sysCombDataCurve[0:dimtmp] = ndCurDay[:]

    # get System Cap up and down real
    # get AC1
    ndRealData = basicGetOneTimePeriodQueryNdArray(idAC1, dayStartTime, formatted_time, accuracy, dataType,
                                                   storageGroupPath)
    # print ('----------------------')
    # print (ndRealData.shape)
    ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndRealData)
    sysCapUp = data_capup_real - ndDataCurve
    sysCapDown = ndDataCurve - data_capdown_real

    # get AC2
    ndRealData = basicGetOneTimePeriodQueryNdArray(idAC2, dayStartTime, formatted_time, accuracy, dataType,
                                                   storageGroupPath)
    ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndRealData)
    sysCapUp = sysCapUp + (data_capup_real - ndDataCurve)
    sysCapDown = sysCapDown + (ndDataCurve - data_capdown_real)

    # get heat pump
    ndRealData = basicGetOneTimePeriodQueryNdArray(idHP, dayStartTime, formatted_time, accuracy, dataType,
                                                   storageGroupPath)
    ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndRealData)
    sysCapUp = sysCapUp + (data_capup_real - ndDataCurve)
    sysCapDown = sysCapDown + (ndDataCurve - data_capdown_real)

    # get PV
    ndRealData = basicGetOneTimePeriodQueryNdArray(idPV, dayStartTime, formatted_time, accuracy, dataType,
                                                   storageGroupPath)
    ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndRealData)
    sysCapUp = sysCapUp + 0.0
    sysCapDown = sysCapDown + ndDataCurve

    # get wind
    ndRealData = abs(
        basicGetOneTimePeriodQueryNdArray(idWind, dayStartTime, formatted_time, accuracy, dataType, storageGroupPath))
    ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndRealData)
    sysCapUp = sysCapUp + 0.0
    sysCapDown = sysCapDown + ndDataCurve

    ndSysCapUpReal = ndCurDay + sysCapUp
    ndSysCapDownReal = ndCurDay - sysCapDown

    '''
    return {'PredValue': sysCombDataCurve,
            'CurrValue': ndCurDay,
            'CapUpReal': ndSysCapUpReal,
            'CapDownReal': ndSysCapDownReal}
    '''

    dftmp = basicGetOneTimePeriodQuery(idTotal, startTime, endTime, accuracy, dataType)
    outList = []
    for idx, irow in dftmp.iterrows():
        outList.append({'PredValue': '%.2f' % (sysCombDataCurve[idx]),
                        'time': datetime.datetime.fromtimestamp(irow[0] / 1000).strftime("%Y-%m-%d_%H:%M")})

    for idx, irow in dftmpCurDay.iterrows():
        outList.append({'CurrValue': '%.2f' % (ndCurDay[idx]),
                        'time': datetime.datetime.fromtimestamp(irow[0] / 1000).strftime("%Y-%m-%d_%H:%M")})

    for idx, irow in dftmpCurDay.iterrows():
        outList.append({'CapUpReal': '%.2f' % (ndSysCapUpReal[idx]),
                        'time': datetime.datetime.fromtimestamp(irow[0] / 1000).strftime("%Y-%m-%d_%H:%M")})

    for idx, irow in dftmpCurDay.iterrows():
        outList.append({'CapDownReal': '%.2f' % (ndSysCapDownReal[idx]),
                        'time': datetime.datetime.fromtimestamp(irow[0] / 1000).strftime("%Y-%m-%d_%H:%M")})

    return outList

@api.method()
# startTime should be '2025-01-10 00:00:00' endTime should be '2025-01-11 00:00:00'
def getVirtualUnitOperData(unitType: str, startTime: str, endTime: str, accuracy: str = 'k5', dataType: str = 'P',
                           storageGroupPath: str = 'root')-> List[dict]:
    # first get unit Predict data for the whole day
    if unitType == 'AC':
        ndPredData = basicGetPredictValue(idAC1, startTime, endTime, accuracy, dataType, storageGroupPath)
        ndPredData += basicGetPredictValue(idAC2, startTime, endTime, accuracy, dataType, storageGroupPath)

    elif unitType == 'HP':
        ndPredData = basicGetPredictValue(idHP, startTime, endTime, accuracy, dataType, storageGroupPath)

    elif unitType == 'PV':
        ndPredData = basicGetPredictValue(idPV, startTime, endTime, accuracy, dataType, storageGroupPath)

    elif unitType == 'Wind':
        ndPredData = basicGetPredictValue(idWind, startTime, endTime, accuracy, dataType, storageGroupPath)
    else:
        return [{'Error':'unitType error: %s' % (unitType)}]

    # get unit Actual value
    # already get the predict value for the whole day,combine with the actual value
    current_time = datetime.datetime.now()

    # 格式化为字符串 "YYYY-MM-DD HH:MM:SS"
    formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
    formatted_day = current_time.strftime("%Y-%m-%d")
    dayStartTime = '%s 00:00:00' % (formatted_day)

    # get the data for the current day
    if unitType == 'AC':
        ndCurDay = basicGetOneTimePeriodQueryNdArray(idAC1, dayStartTime, formatted_time, accuracy, dataType,
                                                     storageGroupPath)
        ndCurDay += basicGetOneTimePeriodQueryNdArray(idAC2, dayStartTime, formatted_time, accuracy, dataType,
                                                      storageGroupPath)

    elif unitType == 'HP':
        ndCurDay = basicGetOneTimePeriodQueryNdArray(idHP, dayStartTime, formatted_time, accuracy, dataType,
                                                     storageGroupPath)

    elif unitType == 'PV':
        ndCurDay = basicGetOneTimePeriodQueryNdArray(idPV, dayStartTime, formatted_time, accuracy, dataType,
                                                     storageGroupPath)

    elif unitType == 'Wind':
        ndCurDay = abs(basicGetOneTimePeriodQueryNdArray(idWind, dayStartTime, formatted_time, accuracy, dataType,
                                                         storageGroupPath))
    else:
        return [{'Error':'unitType error: %s' % (unitType)}]

    unitCombDataCurve = ndPredData.copy()
    dimtmp = ndCurDay.shape[0]
    unitCombDataCurve[0:dimtmp] = ndCurDay[:]

    # get System Cap up and down real
    # get AC1
    if unitType == 'AC':
        ndRealData = basicGetOneTimePeriodQueryNdArray(idAC1, dayStartTime, formatted_time, accuracy, dataType,
                                                       storageGroupPath)
        # print ('----------------------')
        # print (ndRealData.shape)
        ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndRealData)
        unitCapUp = data_capup_real - ndDataCurve
        unitCapDown = ndDataCurve - data_capdown_real

        # get AC2
        ndRealData = basicGetOneTimePeriodQueryNdArray(idAC2, dayStartTime, formatted_time, accuracy, dataType,
                                                       storageGroupPath)
        ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndRealData)
        unitCapUp = unitCapUp + (data_capup_real - ndDataCurve)
        unitCapDown = unitCapDown + (ndDataCurve - data_capdown_real)

    elif unitType == 'HP':
        # get heat pump
        ndRealData = basicGetOneTimePeriodQueryNdArray(idHP, dayStartTime, formatted_time, accuracy, dataType,
                                                       storageGroupPath)
        ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndRealData)
        unitCapUp = data_capup_real - ndDataCurve
        unitCapDown = ndDataCurve - data_capdown_real

    elif unitType == 'PV':

        ndRealData = basicGetOneTimePeriodQueryNdArray(idPV, dayStartTime, formatted_time, accuracy, dataType,
                                                       storageGroupPath)
        ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndRealData)

        unitCapUp = 0.0
        unitCapDown = ndDataCurve

    elif unitType == 'Wind':
        ndRealData = abs(basicGetOneTimePeriodQueryNdArray(idWind, dayStartTime, formatted_time, accuracy, dataType,
                                                           storageGroupPath))
        ndDataCurve, data_capup_real, data_capdown_real = basicGetUpDownCap(ndRealData)
        unitCapUp = 0.0
        unitCapDown = ndDataCurve
    else:
        return [{'Error':'unitType error: %s' % (unitType)}]

    ndUnitCapUpReal = ndCurDay + unitCapUp
    ndUnitCapDownReal = ndCurDay - unitCapDown

    '''
    return {'PredValue': unitCombDataCurve,
            'CurrValue': ndCurDay,
            'CapUpReal': ndUnitCapUpReal,
            'CapDownReal': ndUnitCapDownReal}
    '''

    dftmp = basicGetOneTimePeriodQuery(idTotal, startTime, endTime, accuracy, dataType)
    dftmpCurDay = basicGetOneTimePeriodQuery(idTotal, dayStartTime, formatted_time, accuracy, dataType)
    outList = []
    for idx, irow in dftmp.iterrows():
        outList.append({'PredValue': '%.2f' % (unitCombDataCurve[idx]),
                        'time': datetime.datetime.fromtimestamp(irow[0] / 1000).strftime("%Y-%m-%d_%H:%M")})

    for idx, irow in dftmpCurDay.iterrows():
        outList.append({'CurrValue': '%.2f' % (ndCurDay[idx]),
                        'time': datetime.datetime.fromtimestamp(irow[0] / 1000).strftime("%Y-%m-%d_%H:%M")})

    for idx, irow in dftmpCurDay.iterrows():
        outList.append({'CapUpReal': '%.2f' % (ndUnitCapUpReal[idx]),
                        'time': datetime.datetime.fromtimestamp(irow[0] / 1000).strftime("%Y-%m-%d_%H:%M")})

    for idx, irow in dftmpCurDay.iterrows():
        outList.append({'CapDownReal': '%.2f' % (ndUnitCapDownReal[idx]),
                        'time': datetime.datetime.fromtimestamp(irow[0] / 1000).strftime("%Y-%m-%d_%H:%M")})

    return outList

#-----------------start the app-----------------------------------------------------------
app = jsonrpc.API()
app.bind_entrypoint(api)

#atexit.register(ExecuteBeforeExit)

if __name__ == '__main__':
    import uvicorn
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('--port', '-p', type=int, default=8010)  # app port
    args = parser.parse_args()
    params = vars(args)
    uvicorn.run(app='nybzBuildingAPI:app', port=params['port'],  reload=True)